#include <stddef.h>
#include <pthread.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>
#include "c-utils/internal/timer.h"

typedef struct timer_info
{
    timer_t id;
    unsigned int second;
    int repeat;
    timer_callback_func callback;
    void *param;
    int enable;
} timer_info;

#define MUTILS_MAX_TIMER_COUNT 8
timer_info timer_table[MUTILS_MAX_TIMER_COUNT] = {{0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}};
pthread_mutex_t timer_mtx = PTHREAD_MUTEX_INITIALIZER;

void timer_proc(union sigval v)
{
    int index = v.sival_int;
    timer_callback_func callback = NULL;
    void *param = NULL;

    pthread_mutex_lock(&timer_mtx);
    if (timer_table[index].enable == 1)
    {
        callback = timer_table[index].callback;
        param = timer_table[index].param;
    }
    pthread_mutex_unlock(&timer_mtx);

    if (callback != NULL)
        callback(param);
}

int utils_create_timer(TIMER_TYPE timer_type, timer_callback_func callback, void *param, unsigned int second, int repeat)
{
    timer_t id = 0;
    clockid_t type = CLOCK_REALTIME;
    struct sigevent evp;
    struct itimerspec it;
    int index = 0;

    pthread_mutex_lock(&timer_mtx);
    for (index = 0; index < MUTILS_MAX_TIMER_COUNT; index++)
    {
        if (timer_table[index].enable == 0)
            break;
    }

    if (index == MUTILS_MAX_TIMER_COUNT)
    {
        pthread_mutex_unlock(&timer_mtx);
        return -1;
    }

    memset(&evp, 0, sizeof(struct sigevent));
    evp.sigev_value.sival_int = index;
    evp.sigev_notify = SIGEV_THREAD;
    evp.sigev_notify_function = timer_proc;

    if (timer_type == TIMER_TYPE_REALTIME)
        type = CLOCK_REALTIME;
    else if (timer_type == TIMER_TYPE_ALARM)
#ifdef CLOCK_REALTIME_ALARM
        type = CLOCK_REALTIME_ALARM;
#else
        type = 8;
#endif

    if (timer_create(type, &evp, &id) == -1)
    {
        pthread_mutex_unlock(&timer_mtx);
        return -1;
    }
    memset(&it, 0, sizeof(struct itimerspec));

    it.it_value.tv_sec = second;
    it.it_value.tv_nsec = 0;
    if (repeat == 1)
    {
        it.it_interval.tv_sec = second;
        it.it_interval.tv_nsec = 0;
    }

    if (timer_settime(id, 0, &it, NULL) == -1)
    {
        pthread_mutex_unlock(&timer_mtx);
        return -1;
    }

    timer_table[index].id = id;
    timer_table[index].second = second;
    timer_table[index].repeat = repeat;
    timer_table[index].callback = callback;
    timer_table[index].param = param;
    timer_table[index].enable = 1;
    pthread_mutex_unlock(&timer_mtx);

    return index + 1;
}

int utils_delete_timer(int timer_id)
{
    int index = 0;

    if (timer_id <= 0 || timer_id > MUTILS_MAX_TIMER_COUNT)
        return -1;

    index = timer_id - 1;
    pthread_mutex_lock(&timer_mtx);
    if (timer_table[index].enable == 1)
    {
        timer_delete(timer_table[index].id);
        timer_table[index].enable = 0;
        timer_table[index].param = NULL;
        timer_table[index].callback = NULL;
        timer_table[index].repeat = 0;
        timer_table[index].second = 0;
        timer_table[index].id = 0;
    }
    pthread_mutex_unlock(&timer_mtx);

    return 0;
}

uint64_t utils_time_usec()
{
    uint64_t usec;
    struct timeval tv;

    gettimeofday(&tv, NULL);
    usec = tv.tv_sec * 1000LL * 1000LL + tv.tv_usec;
    return usec;
}

uint64_t utils_time_msec()
{
    return (utils_time_usec() / 1000LL);
}